//
// Permission to distribute this example by
// Copyright (C) 2007 Ralf Junker
// Ralf Junker <delphi@yunqa.de>
// http://www.yunqa.de/delphi/

//---------------------------------------------------------------------------

#include <stdio.h>
#include <sys/stat.h>
#include "xdelta/xdelta3.h"
#include "xdelta/xdelta3.c"
#include <jni.h>
#include "md5.h"
#include <string.h>

//---------------------------------------------------------------------------
#ifdef __cplusplus
extern "C" {
#endif

int xdelta3_code(int encode, FILE* InFile, FILE* SrcFile, FILE* OutFile, int BufSize) {
	int r, ret;
	struct stat statbuf;
	xd3_stream stream;
	xd3_config config;
	xd3_source source;
	uint8_t * Input_Buf;
	int Input_Buf_Read;

	if (BufSize < XD3_ALLOCSIZE)
		BufSize = XD3_ALLOCSIZE;

	memset(&stream, 0, sizeof(stream));
	memset(&source, 0, sizeof(source));

	xd3_init_config(&config, XD3_ADLER32);
	config.winsize = BufSize;
	xd3_config_stream(&stream, &config);

	if (SrcFile) {
		r = fstat(fileno(SrcFile), &statbuf);
		if (r)
			return r;

		source.blksize = BufSize;
		source.curblk = (uint8_t *) malloc(source.blksize);

		/* Load 1st block of stream. */
		r = fseek(SrcFile, 0, SEEK_SET);
		if (r)
			return r;
		source.onblk = fread((void*) source.curblk, 1, source.blksize, SrcFile);
		source.curblkno = 0;
		/* Set the stream. */
		xd3_set_source(&stream, &source);
	}

	Input_Buf = (uint8_t *) malloc(BufSize);

	fseek(InFile, 0, SEEK_SET);
	do {
		Input_Buf_Read = fread(Input_Buf, 1, BufSize, InFile);
		if (Input_Buf_Read < BufSize) {
			xd3_set_flags(&stream, XD3_FLUSH | stream.flags);
		}
		xd3_avail_input(&stream, Input_Buf, Input_Buf_Read);

		process: if (encode) {
			ret = xd3_encode_input(&stream);
		} else {
			ret = xd3_decode_input(&stream);
		}

		switch (ret) {
		case XD3_INPUT: {
			continue;
		}
		case XD3_OUTPUT: {
			r = fwrite(stream.next_out, 1, stream.avail_out, OutFile);
			if (r != (int) stream.avail_out)
				return r;
			xd3_consume_output(&stream);
			goto process;
		}
		case XD3_GETSRCBLK: {
			if (SrcFile) {
				r = fseek(SrcFile, source.blksize * source.getblkno, SEEK_SET);
				if (r)
					return r;
				source.onblk = fread((void*) source.curblk, 1, source.blksize,
						SrcFile);
				source.curblkno = source.getblkno;
			}
			goto process;
		}
		case XD3_GOTHEADER: {
			goto process;
		}
		case XD3_WINSTART: {
			goto process;
		}
		case XD3_WINFINISH: {
			goto process;
		}
		default: {
			return ret;
		}

		}

	} while (Input_Buf_Read == BufSize);

	free(Input_Buf);

	free((void*) source.curblk);
	xd3_close_stream(&stream);
	xd3_free_stream(&stream);

	return 0;
}
#ifdef __cplusplus
}
#endif

extern "C" {

int xdelta3_patch(
        int opType,         //1 gen patch, 0 apply patch
        const char *inPath,  //老版本文件    补丁文件
        const char *srcPath, //新版本文件	老版本文件
        const char *outPath  //文件补丁		新版本文件
) {
    int res;
    FILE *InFile;
    FILE *SrcFile;
    FILE *OutFile;

    InFile = fopen(inPath, "rb");
    if (InFile == NULL) {
        res = -1;
        goto out3;
    }

    SrcFile = fopen(srcPath, "rb");
    if (SrcFile == NULL) {
        res = -2;
        goto out2;
    }

    OutFile = fopen(outPath, "wb");
    if (OutFile == NULL) {
        res = -3;
        goto out1;
    }

    res = xdelta3_code(opType, InFile, SrcFile, OutFile, 0x1000);

    fclose(OutFile);
    out1:
    fclose(SrcFile);
    out2:
    fclose(InFile);
    out3:
    return res;
}

#define BUF_SIZE (8 * 1024)

/**
 * 计算文件md5
 * @param filePath 文件路径
 * @param result 存储计算结果
 * @return 0 if succ
 */
int xdelta3_filemd5(const char *filePath, char* result)
{
	uint32_t readLen;
	MD5 md5;
	FILE *file = fopen(filePath, "rb");
	if(!file)
		return 1;
	uint8_t buffer[BUF_SIZE];
	while ((readLen = fread(buffer, 1, BUF_SIZE, file)) != 0)
		md5.add(buffer, readLen);
	fclose(file);
	std::string md5Str = md5.getHash();
	memcpy(result, md5Str.data(), 33);
	return 0;
}

}
